import os
import hashlib
import shutil
import subprocess
import win32con
import win32ui
import re
import tkinter as tk
from tkinter import filedialog
from loger_setup import logger as log


def is_path_in_list(path, path_list):
    """检查给定路径是否存在于路径列表中"""
    # 规范化目标路径
    normalized_path = os.path.normpath(path)

    # 使用集合和map函数规范化路径列表并检查是否存在
    return normalized_path in set(map(os.path.normpath, path_list))


def compare_files_by_hash(file1, file2):
    """使用哈希方法比较两个文件的内容是否相同"""
    def get_file_hash(file_path):
        """获取文件内容的哈希值"""
        hasher = hashlib.md5()
        with open(file_path, 'rb') as file:
            buf = file.read()
            hasher.update(buf)
        return hasher.hexdigest()
    try:
        hash1 = get_file_hash(file1)
        hash2 = get_file_hash(file2)
        return hash1 == hash2
    except FileNotFoundError as e:
        log.info(f"File not found: {e}")
        return False
    except Exception as e:
        log.info(f"An error occurred: {e}")
        return False


def copy_file(src, dst):
    """使用 shutil.copy2 复制文件，支持覆盖和重命名"""
    try:
        # 确保目标目录存在
        os.makedirs(os.path.dirname(dst), exist_ok=True)
        # 复制文件并保留更多元数据
        shutil.copy2(src, dst)
        log.info(f"成功复制文件从 {src} 到 {dst}")
        return True
    except FileNotFoundError as e:
        info = f"从{src}复制到{dst}时出现错误：找不到文件，无法复制"
        log.error(info)
        return info
    except PermissionError as e:
        info = f"从{src}复制到{dst}时出现错误：权限不足，无法复制"
        log.error(info)
        return info
    except Exception as e:
        info = f"从{src}复制到{dst}时出现错误: {e}"
        log.error(info)
        return info


def is_sub_path(path, dir_path):
    """
    判断给定的路径是否位于指定目录或其子目录中。

    :param path: 要检查的路径 (字符串)
    :param dir_path: 基准目录路径 (字符串)
    :return: 如果 path 位于 dir_path 或其子目录中则返回 True，否则返回 False
    """
    try:
        # 获取两个路径的绝对路径
        abs_path = os.path.abspath(path)
        abs_dir_path = os.path.abspath(dir_path)
        # 使用 commonpath 找出公共路径部分
        common = os.path.commonpath([abs_path, abs_dir_path])
        # 检查公共路径是否等于基准目录路径
        return common == abs_dir_path
    except Exception as e:
        log.error(f"发生错误: {e}")
        return False


def get_relative_path(full_path, project_dir):
    # 规范化路径以确保一致性
    full_path_normalized = os.path.normpath(full_path)
    project_dir_normalized = os.path.normpath(project_dir)

    # 获取相对路径
    relative_path = os.path.relpath(full_path_normalized, project_dir_normalized)

    # 检查是否相对路径包含 ..
    if '..' in relative_path.split(os.sep)[0]:
        return None  # 如果路径不在项目目录下，返回 None 或者其他提示

    # 将路径中的反斜杠替换为正斜杠，以便输出统一格式
    normalized_path = relative_path.replace(os.sep, '/')

    return normalized_path


def walk_file(path, ext=None, not_in=None, is_in=None, get_full_paths=True, deep=True):
    """
    列出文件夹中的文件, 并根据后缀、关键字筛选出来
    注意：若get_full_paths为False时，可能得到重复的文件名
    :param path: 根目录
    :param ext: 需要的文件的后缀名,可为单个后缀，或后缀列表。例如：'exe'或['exe', 'txt']
    :param not_in: 文件名中不能包含的字符。例如：'~$'或['~$', 'tmp']
    :param is_in: 文件名中必须包含的字符。例如：'abc'或['abc', 'def']
    :param get_full_paths: 默认True时返回文件的完整路径列表，否则返回文件名列表
    :param deep: 默认True，表示深度遍历所有子文件夹下的文件；若为False，只获取当前目录path下的文件
    :return: [files] or [file_paths]
    """
    # 检查给定路径是否为目录，如果不是则返回None
    if not os.path.isdir(path):
        return None

    # 参数规范化函数，将参数转换为小写列表
    def normalize_param(param):
        if param is None:
            return None
        if not isinstance(param, list):
            return [param.lower()]
        return [x.lower() for x in param]

    # 规范化处理ext，not_in，is_in参数
    ext = normalize_param(ext)
    not_in = normalize_param(not_in)
    is_in = normalize_param(is_in)

    # 初始化文件列表
    filelist = []

    # 根据deep参数决定是否深度遍历
    if not deep:
        # 只遍历当前目录
        for file in os.listdir(path):
            one_file_path = os.path.join(path, file)
            file_name = os.path.basename(one_file_path)
            f_ext = os.path.splitext(file_name)[1].lower()

            # 根据文件后缀，not_in，is_in筛选文件
            if ext is None or f_ext[1:] in ext:
                if not_in is None or all(ni not in file_name for ni in not_in):
                    if is_in is None or any(ii in file_name for ii in is_in):
                        # 根据get_full_paths参数决定添加文件的完整路径还是文件名
                        if get_full_paths:
                            filelist.append(os.path.abspath(one_file_path))
                        else:
                            filelist.append(file)
    else:
        # 深度遍历
        for root, _, files in os.walk(path):
            for file in files:
                one_file_path = os.path.join(root, file)
                file_name = os.path.basename(one_file_path)
                f_ext = os.path.splitext(file_name)[1].lower()
                # 同样地筛选逻辑
                if ext is None or f_ext[1:] in ext:
                    if not_in is None or all(ni not in file_name for ni in not_in):
                        if is_in is None or any(ii in file_name for ii in is_in):
                            # 根据get_full_paths参数决定添加文件的完整路径还是文件名
                            if get_full_paths:
                                filelist.append(os.path.abspath(one_file_path))
                            else:
                                filelist.append(file)
    # 返回文件列表
    return filelist


def open_folder_and_select_file(file_path):
    # folder_path = os.path.dirname(file_path)
    file_name = os.path.basename(file_path)
    # # 构建完整的文件路径
    # file_path = os.path.join(folder_path, file_name)
    # 检查文件是否存在
    if not os.path.exists(file_path):
        log.info(f"文件 {file_path} 不存在。")
        return
    # 使用 explorer /select 命令打开文件夹并选中文件
    subprocess.run(['explorer', '/select,', file_name])


def create_folder(path, empty_folder=True):
    try:
        # 如果文件夹存在且不为空，且 allow_non_empty 为 True，则删除文件夹
        if os.path.exists(path) and os.path.isdir(path):
            if os.listdir(path):  # 文件夹不为空
                if empty_folder:
                    shutil.rmtree(path)
                    log.info(f"文件夹 '{path}' 已存在且包含文件，已删除并重新创建。")
                    os.makedirs(path, exist_ok=True)
                else:
                    log.info(f"文件夹 '{path}' 已存在且包含文件，未删除。")
            else:  # 文件夹为空
                log.info(f"文件夹 '{path}' 已存在且为空。")
        else:
            # 文件夹不存在，创建文件夹
            os.makedirs(path, exist_ok=True)
            log.info(f"文件夹 '{path}' 已创建。")
    except PermissionError:
        log.info(f"权限不足，无法在 {path} 创建文件夹。")
    except FileNotFoundError:
        log.info(f"父目录不存在，无法在 {path} 创建文件夹。")
    except OSError as e:
        log.info(f"创建文件夹时发生错误: {e}")


def copy_files_and_folders(src, dst, empty_folder=False):
    """
    复制src目录下的所有文件和文件夹到dst目录。
    如果有同名文件则覆盖，如果有同名文件夹，则原文件夹保留不变，新文件直接复制，同名文件覆盖。
    empty_folder: 是否在复制前清空旧文件夹
    """
    # 验证输入路径
    src = os.path.abspath(src)
    dst = os.path.abspath(dst)

    if not os.path.isdir(src):
        log.error(f"Source directory does not exist: {src}")
        return '待复制的路径不存在'
    try:
        # 确保目标目录存在
        create_folder(dst, empty_folder)
        for item in os.listdir(src):
            src_item = os.path.join(src, item)
            dst_item = os.path.join(dst, item)
            if os.path.isdir(src_item):
                # log.info(f"Directory found: {src_item}")
                if not os.path.exists(dst_item):
                    shutil.copytree(src_item, dst_item)
                    log.info(f"复制新路径: {dst_item}")
                else:
                    log.info(f"路径 {dst_item} 已经存在, 复制中...")
                    copy_files_and_folders(src_item, dst_item)
            else:
                # log.info(f"File found: {src_item}")
                try:
                    shutil.copy2(src_item, dst_item)
                    # log.info(f"Copied new file: {dst_item}")
                except shutil.SameFileError:
                    log.info(f"错误，原路径和新路径重复: {dst_item}")
                except Exception as e:
                    log.error(f"复制文件 {src_item} 到 {dst_item} 时错误: {e}")
    except Exception as e:
        log.error(f"复制过程中发生了错误，在处理{src}时: {e}")
        return f'复制过程中发生了错误，在处理{src}时: {e}'
    return True


def open_Files_window(default_path='.', file_type='Excel Files (*.xls;*.xlsx)|*.xls;*.xlsx|All Files (*.*)|*.*|',
                      api_flag='多选') -> list:
    API_flag = {'单选': win32con.OFN_FILEMUSTEXIST,
                '多选': win32con.OFN_ALLOWMULTISELECT,
                '覆盖': win32con.OFN_OVERWRITEPROMPT}

    # 验证 api_flag 是否合法
    if api_flag not in API_flag:
        raise ValueError("无效的 api_flag 参数")

    # 验证 default_path 是否合法
    if not default_path or not os.path.isdir(default_path):
        raise ValueError("默认路径无效或为空")

        # 验证 file_type 格式是否正确
    if not isinstance(file_type, str) or not re.match(r'^[^|]+\|\*[\.\w;]*\|$', file_type):
        raise ValueError("文件类型格式不正确，应为 '描述 (扩展名)|扩展名|' 的形式")

    cached_dlg = win32ui.CreateFileDialog(1, None, None, API_flag[api_flag], file_type)
    cached_dlg.SetOFNInitialDir(default_path)
    try:
        if cached_dlg.DoModal() == win32con.IDOK:
            path_list = cached_dlg.GetPathNames()
        else:
            path_list = []  # 用户取消了对话框
    except Exception as e:
        log.error(f"发生错误: {e}")
        path_list = []

    return path_list


def delete_path(path):
    try:
        if os.path.isfile(path):
            os.remove(path)
            log.info(f"文件 '{path}' 已成功删除")
        elif os.path.isdir(path):
            if not os.listdir(path):  # 检查目录是否为空
                os.rmdir(path)
                log.info(f"空目录 '{path}' 已成功删除")
            else:
                shutil.rmtree(path)
                log.info(f"目录 '{path}' 及其内容已成功删除")
        else:
            log.info(f"路径 '{path}' 既不是文件也不是目录")
    except FileNotFoundError:
        log.error(f"路径 '{path}' 不存在")
    except PermissionError:
        log.error(f"没有权限删除路径 '{path}'")
    except OSError as e:
        log.error(f"删除路径时发生错误: {e}")


def save_file_dialog(options=None):
    # 定义文件对话框选项
    if not options:
        options = {
            'defaultextension': '.json',
            'filetypes': [('JSON files', '*.json')],
            'initialfile': 'option.json',
            'title': 'Save JSON File'
        }
    if not isinstance(options, dict):
        raise '保存文件窗口的参数options错误'
    # 创建Tkinter根窗口（不需要显示）
    root = tk.Tk()
    root.withdraw()  # 隐藏主窗口
    # 打开保存文件对话框
    file_path = filedialog.asksaveasfilename(**options)
    if file_path:
        # 如果用户选择了文件并点击了保存按钮，则处理所选文件路径
        log.info(f"Selected file path: {file_path}")
        # 这里可以添加代码来实际保存文件内容到指定路径
    else:
        # 用户取消了操作
        log.info("Save operation cancelled")
    root.destroy()